home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / sbin / update-python-modules < prev    next >
Encoding:
Text File  |  2008-10-27  |  14.3 KB  |  390 lines

  1. #! /usr/bin/python
  2. #
  3. # copyright (c) 2006 Josselin Mouette <joss@debian.org>
  4. # Licensed under the GNU Lesser General Public License, version 2.1
  5. # See COPYING for details
  6.  
  7. import sys,os,shutil
  8. from optparse import OptionParser
  9. from py_compile import compile, PyCompileError
  10. sys.path.append("/usr/lib/python-support/private/")
  11. import pysupport
  12. from pysupport import py_supported,py_installed,py_oldversions
  13.  
  14. basepath='/var/lib/python-support'
  15. sourcepath='/usr/share/python-support'
  16. extensionpath='/usr/lib/python-support'
  17.  
  18. parser = OptionParser(usage="usage: %prog [-v] [-c] package_directory [...]\n"+
  19.                             "       %prog [-v] [-c] package.dirs [...]\n"+
  20.                             "       %prog [-v] [-a|-f|-p]")
  21. parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
  22.                   help="verbose output", default=False)
  23. parser.add_option("-c", "--clean", action="store_true", dest="clean_mode",
  24.                   help="clean modules instead of compiling them",
  25.                   default=False)
  26. parser.add_option("-a", "--rebuild-all", action="store_true",
  27.                   dest="rebuild_all", default=False,
  28.                   help="rebuild all private modules for a new default python version")
  29. parser.add_option("-f", "--force-rebuild-all", action="store_true",
  30.                   dest="rebuild_everything", default=False,
  31.                   help="rebuild all modules, including public modules for all python versions")
  32. parser.add_option("-p", "--post-install", action="store_true", dest="post_install",
  33.                   help="run post-installation operations, common to many packages",
  34.                   default=False)
  35. parser.add_option("-b", "--bytecompile", action="store_true", dest="force_private",
  36.                   help="[deprecated] byte-compilation mode: only handle private modules",
  37.                   default=False)
  38. parser.add_option("-i", "--install", action="store_true", dest="force_public",
  39.                   help="[deprecated] installation mode: only handle public modules",
  40.                   default=False)
  41. (options, args) = parser.parse_args()
  42.  
  43. def debug(x):
  44.   if(options.verbose):
  45.     print x
  46.  
  47. # I should use the sets type instead
  48. def isect(l1,l2):
  49.   return [i for i in l1 if i in l2]
  50.   
  51. def concat(l1,l2):
  52.   return l1 + [i for i in l2 if i not in l1]
  53.  
  54. versions_dict={}
  55.  
  56. def dir_versions(dir):
  57.   if dir not in versions_dict:
  58.     verfile=os.path.join(dir,'.version')
  59.     if dir.startswith(extensionpath):
  60.       # Directory in /usr/lib: only one version
  61.       vers=os.path.split(dir)[1]
  62.       if vers in py_supported:
  63.         versions_dict[dir]=[vers]
  64.       else:
  65.         versions_dict[dir]=[]
  66.     elif dir.startswith(sourcepath):
  67.       # Directory in /usr/share
  68.       extdir=dir.replace(sourcepath,extensionpath,1)
  69.       if os.path.exists(verfile):
  70.         # If we have a .version, use it
  71.         versions_dict[dir]=pysupport.version_list(file(verfile).readline())
  72.       elif os.path.isdir(extdir):
  73.         # Try to obtain the list of supported versions
  74.         # from the extensions in /usr/lib
  75.         versions_dict[dir]=isect(py_supported,os.listdir(extdir))
  76.       else:
  77.         # Otherwise, support all versions
  78.         versions_dict[dir]=py_supported
  79.     else:
  80.       raise "[Internal error] %s: unsupported path for byte-compilation."
  81.   return versions_dict[dir]
  82.  
  83. def bytecompile_only(basedir,dir,file):
  84.   if file.endswith('.py'):
  85.     fullpath=os.path.join(basedir,dir,file)
  86.     debug("compile "+fullpath+'c')
  87.     try:
  88.       # Note that compile doesn't raise PyCompileError by default
  89.       compile(fullpath, doraise=True)
  90.     except IOError, (errno, strerror):
  91.       sys.stderr.write("WARNING: I/O error while trying to byte-compile %s (%s): %s\n" % (fullpath, errno, strerror))
  92.     except PyCompileError, inst:
  93.       sys.stderr.write("WARNING: compile error while trying to byte-compile %s: %s\n" % (fullpath, inst.msg))
  94.     except:
  95.       sys.stderr.write("WARNING: unexpected error while trying to byte-compile %s: %s\n" % (fullpath, sys.exc_info()[0]))
  96.  
  97. def clean_simple(basedir,dir,file):
  98.   if file.endswith('.py'):
  99.     for ext in ['c','o']:
  100.       fullpath=os.path.join(basedir,dir,file+ext)
  101.       if os.path.exists(fullpath):
  102.         debug("remove "+fullpath)
  103.         os.remove(fullpath)
  104.  
  105. def install_modules(versions):
  106.   def install_modules_func(basedir,dir,file):
  107.     if file == '.version':
  108.       return
  109.     fullpath=os.path.join(basedir,dir,file)
  110.     for py in isect(dir_versions(basedir),versions):
  111.       destpath=os.path.join(basepath,py,dir,file)
  112.       try:
  113.         os.makedirs(os.path.join(basepath,py,dir))
  114.       except OSError:
  115.         pass
  116.       if file[-4:] not in ['.pyc','.pyo']:
  117.         debug("link "+destpath)
  118.         # os.path.exists returns False for broken symbolic links
  119.         if os.path.exists(destpath) or os.path.islink(destpath):
  120.           if file!="__init__.py" or (os.path.exists(destpath) and os.path.getsize(destpath)):
  121.             # Oops, the file already exists and is not empty.
  122.             # Check whether we are conflicting with something else.  
  123.             for otherdir in dirs_i:
  124.               otherextdir = os.path.join(otherdir.replace(sourcepath,extensionpath,1),py)
  125.               if basedir in [otherdir,otherextdir]:
  126.                 continue
  127.               if os.path.exists(os.path.join(otherdir,dir,file)) or os.path.exists(os.path.join(otherextdir,dir,file)):
  128.                 sys.stderr.write("Package %s is trying to overwrite %s which is already provided by %s\n"%(os.path.basename(basedir),os.path.join(dir,file),os.path.basename(otherdir)))
  129.                 sys.exit(1)
  130.             # The file is already here, probably from the previous version. 
  131.             # Let's proceed.
  132.             debug("overwrite "+destpath)
  133.           else:
  134.             debug("overwrite namespace "+destpath)
  135.           os.remove(destpath)
  136.         os.symlink(fullpath,destpath)
  137.       # Files are NOT byte-compiled here, this will be done later.
  138.   return install_modules_func
  139.  
  140. def process(basedir,func):
  141.   debug("Looking at %s..."%(basedir))
  142.   for dir, dirs, files in os.walk(basedir):
  143.     dir = dir[len(basedir):].lstrip('/')
  144.     for file in files:
  145.       func(basedir, dir, file)
  146.     for file in dirs:
  147.       if os.path.islink(os.path.join(basedir,dir,file)):
  148.         func(basedir, dir, file)
  149.  
  150. def process_extensions(basedir,func,version=None):
  151.   basedir=basedir.replace(sourcepath,extensionpath,1)
  152.   if os.path.isdir(basedir):
  153.     for vers in os.listdir(basedir):
  154.       if version and vers != version:
  155.         continue
  156.       verdir=os.path.join(basedir,vers)
  157.       if os.path.isdir(verdir):
  158.         process(verdir,func([vers]))
  159.  
  160. def dirlist_file(f):
  161.   return [ l.rstrip('\n') for l in file(f) if len(l)>1 ]
  162.  
  163. def bytecompile_all(py,path=None):
  164.   if not path:
  165.     path=os.path.join(basepath,py)
  166.   if not os.path.isdir(path):
  167.     return
  168.   debug("Byte-compilation of whole %s..."%path)
  169.   os.spawnl(os.P_WAIT, '/usr/bin/'+py, py,
  170.             os.path.join('/usr/lib/',py,'compileall.py'), '-q', path)
  171.  
  172. def bytecompile_privatedir(basedir):
  173.   versionfile=os.path.join(basedir,".pyversion")
  174.   if os.path.isfile(versionfile):
  175.     specific_version=file(versionfile).readline().rstrip('\n')
  176.     bytecompile_all("python"+specific_version,basedir)
  177.   else:
  178.     process(basedir,bytecompile_only)
  179.  
  180. def create_dotpath(py,dirhash=None):
  181.   path=os.path.join(basepath,py)
  182.   pathfile=os.path.join(path,".path")
  183.   debug("Generation of %s..."%pathfile)
  184.   pathlist=[path]
  185.   for f in os.listdir(path):
  186.     f=os.path.join(path,f)
  187.     if f.endswith(".pth") and os.path.isfile(f):
  188.       for l in file(f):
  189.         l=l.rstrip('\n')
  190.         if l.startswith('import'):
  191.           # Do not ship lines starting with "import", they are executed! (complete WTF)
  192.           continue
  193.         pathlist.append(l)
  194.         l2=os.path.join(path,l)
  195.         pathlist.append(l2)
  196.         if dirhash:
  197.           dirhash[l2]=False
  198.   fd=file(pathfile,"w")
  199.   fd.writelines([l+'\n' for l in pathlist])
  200.   fd.close()
  201.  
  202. def post_change_stuff(py):
  203.   # All the changes that need to be done after anything has changed
  204.   # in a /var/lib/python-support/pythonX.Y directory
  205.   # * Cleanup of all dangling symlinks that are left out after a package
  206.   #   is upgraded/removed.
  207.   # * The namespace packages are here because python doesn't consider a
  208.   #   directory to be able to contain packages if there is no __init__.py
  209.   #   file (yes, this is completely stupid).
  210.   # * The .path file must be created by concatenating all those .pth
  211.   #   files that extend sys.path (this also badly sucks).
  212.   # * Byte-compilation of all .py files that haven't already been
  213.   path=os.path.join(basepath,py)
  214.   if not os.path.isdir(path):
  215.     return
  216.   # First, remove any dangling symlinks.
  217.   # In the same loop, we find which directories may need a namespace package
  218.   dirhash={}
  219.   for dir, dirs, files in os.walk(path):
  220.     dirhash[dir]=False
  221.     files.sort() # We need the .py to appear before the .pyc
  222.     for f in files+dirs:
  223.       # We also examine dirs as some symlinks are dirs
  224.       abspath=os.path.join(dir,f)
  225.       islink=os.path.islink(abspath)
  226.       if islink:
  227.         if not os.path.exists(abspath):
  228.           # We refer to a file that was removed
  229.           debug("remove "+abspath)
  230.           os.remove(abspath)
  231.           continue
  232.         srcfile = os.readlink (abspath)
  233.         # Remove links left here after a change in the supported python versions for a package
  234.         if srcfile.startswith(sourcepath):
  235.           if py not in dir_versions (os.path.join(sourcepath,srcfile[len(sourcepath)+1:].split("/",1)[0])):
  236.             debug("remove "+abspath)
  237.             os.remove(abspath)
  238.             continue
  239.       if f[-4:] in ['.pyc', '.pyo']:
  240.         if not os.path.exists(abspath[:-1]):
  241.           debug("remove "+abspath)
  242.           os.remove(abspath)
  243.           continue
  244.       elif f[-3:] in ['.py', '.so']:
  245.         if islink or f!='__init__.py':
  246.           # List the directory as maybe needing a namespace packages
  247.           d=dir
  248.           while dirhash.has_key(d) and not dirhash[d]:
  249.             dirhash[d]=True
  250.             d=os.path.dirname(d)
  251.     # Remove the directory if it is empty after our crazy removals
  252.     try:
  253.       os.removedirs(dir)
  254.     except OSError:
  255.       pass
  256.   dirhash[path]=False
  257.   # Then, find which directories belong in a .pth file
  258.   # These directories don't need a namespace package, so we
  259.   # pass the dirhash
  260.   create_dotpath(py,dirhash)
  261.   # Finally, create/remove namespace packages
  262.   for dir in dirhash:
  263.     initfile=os.path.join(dir,"__init__.py")
  264.     noinitfile=os.path.join(dir,".noinit")
  265.     if dirhash[dir] and not os.path.exists(noinitfile):
  266.       if not os.path.exists(initfile):
  267.         debug("create namespace "+initfile)
  268.         file(initfile,"w").close()
  269.     else:
  270.       for e in ['','c','o']:
  271.         if os.path.exists(initfile+e):
  272.           debug('remove namespace '+initfile+e)
  273.           os.remove(initfile+e)
  274.       try:
  275.         os.removedirs(dir)
  276.       except OSError:
  277.         pass
  278.   bytecompile_all(py)
  279.  
  280. # Parse arguments
  281. do_dirs_i=[]
  282. do_dirs_b=[]
  283. for arg in args:
  284.   if os.path.isabs(arg):
  285.     if not arg.startswith(sourcepath):
  286.       parser.error("%s is not in the python-support directory."%arg)
  287.   else:
  288.     arg=os.path.join(sourcepath,arg)
  289.   if not os.path.exists(arg):
  290.     if options.clean_mode:
  291.       sys.stderr.write("WARNING: %s does not exist.\n         Some bytecompiled files may be left behind.\n"%arg)
  292.       continue
  293.     else:
  294.       parser.error("%s does not exist"%arg)
  295.   if arg.endswith('.dirs'):
  296.     do_dirs_b+=dirlist_file(arg)
  297.     if options.force_public:
  298.       parser.error("Option -i cannot be used with a private module .dirs file.")
  299.   elif os.path.isdir(arg):
  300.     do_dirs_i.append(arg)
  301.     if options.force_private:
  302.       parser.error("Option -b cannot be used with a public module directory.")
  303.   else:
  304.     parser.error("%s is not a directory"%arg)
  305.  
  306.  
  307. # Read full list from the source directory
  308. # directories are stuff to be installed
  309. # foo.dirs files list directories to bytecompile in place
  310. dirs_b = []
  311. dirs_i = []
  312. for f in os.listdir(sourcepath):
  313.   f=os.path.join(sourcepath,f)
  314.   if os.path.isdir(f):
  315.     dirs_i.append(f)
  316.   elif f.endswith('.dirs'):
  317.     dirs_b+=dirlist_file(f)
  318.  
  319. if not os.path.isdir(basepath):
  320.   os.mkdir(basepath)
  321.  
  322. if options.rebuild_everything:
  323.   options.rebuild_all = True
  324.   for pyver in py_supported:
  325.     dir = os.path.join(basepath,pyver)
  326.     if os.path.isdir(dir):
  327.       shutil.rmtree(dir)
  328.  
  329. # Check for changes in installed python versions
  330. for pyver in py_oldversions+py_supported:
  331.   dir = os.path.join(basepath,pyver)
  332.   # Check for ".path" because sometimes the directory already exists 
  333.   # while the python version isn't installed, because of some .so's.
  334.   if pyver in py_installed and not os.path.isfile(os.path.join(dir,".path")):
  335.     debug("Building all modules in %s..."%(dir))
  336.     for basedir in dirs_i:
  337.       process(basedir,install_modules([pyver]))
  338.       process_extensions(basedir,install_modules,pyver)
  339.     # Here we need to launch post_change_stuff because otherwise we could
  340.     # end up without the .path file that is checked 6 lines earlier
  341.     post_change_stuff (pyver)
  342.   if pyver not in py_installed and os.path.isdir(dir):
  343.     debug("Removing obsolete directory %s..."%(dir))
  344.     shutil.rmtree(dir)
  345.  
  346. if options.rebuild_all:
  347.   for basedir in dirs_b:
  348.     process(basedir,clean_simple)
  349.     bytecompile_privatedir(basedir)
  350.  
  351.  
  352. # Now for the processing of what was handed on the command line
  353. for basedir in do_dirs_b:
  354.   if not options.clean_mode:
  355.     bytecompile_privatedir(basedir)
  356.   else:
  357.     process(basedir,clean_simple)
  358.  
  359. need_dotpath = False
  360. need_postinstall = []
  361. for basedir in do_dirs_i:
  362.   need_postinstall = concat(need_postinstall,isect(dir_versions(basedir),py_installed))
  363.   if not options.clean_mode:
  364.     process(basedir,install_modules(py_installed))
  365.     process_extensions(basedir,install_modules)
  366.     for f in os.listdir(basedir):
  367.       if f.endswith(".pth"):
  368.         need_dotpath = True
  369.  
  370. # Only do the funny and time-consuming things when the -p option is
  371. # given, e.g when python-support is triggered.
  372. if need_postinstall and 'DPKG_RUNNING_VERSION' in os.environ and not options.post_install:
  373.   ret = os.spawnlp(os.P_WAIT, 'dpkg-trigger', 'dpkg-trigger', '--no-await', 'pysupport')
  374.   if ret:
  375.     sys.stderr.write("ERROR: dpkg-trigger failed\n")
  376.     sys.exit(1)
  377.   need_postinstall = []
  378.  
  379. if options.post_install:
  380.   # Now the trigger is activated, do it for all installed versions
  381.   need_postinstall = py_installed
  382. if need_postinstall:
  383.   need_dotpath = False
  384.   for py in need_postinstall:
  385.     post_change_stuff(py)
  386.  
  387. if need_dotpath:
  388.   for py in need_postinstall:
  389.     create_dotpath (py)
  390.